#include "SharedUtil.hpp"

#include <QMap>
#include <QtDebug>

float eps = 0.000001f;

QPolygonF sortPoints( const QPointF& pt, const QPolygonF& polyline )
{
  QPolygonF result;

  QMap<qreal, int> indices;
  for( int idx = 0; idx < polyline.count(); ++idx ) {
    indices[QLineF(pt, polyline.at( idx )).length()] = idx;
  }

  QList<qreal> values = indices.keys();

  Q_FOREACH( int idx, indices.values() ) {
    result << polyline.at( idx );
  }

  return result;
}

QVector<QRectF> getBoundingRects( const QPolygonF& polyline )
{
  QVector<QRectF> result( polyline.count()-1 );
  for( int idx = 0; idx < result.count(); ++idx ) {
    result[idx] = SharedUtil::getBoundingRect(polyline.at(idx), polyline.at(idx+1));
  }
  return result;
}

//////////////////////////////////////////////////////////////////////////

bool SharedUtil::isPointOnLine( const QPointF& pt, const QLineF& segment )
{
  return qFuzzyCompare( QLineF(pt, segment.p1()).length()+QLineF(pt, segment.p2()).length(), segment.length() );

  bool result = false;
  qreal xmin = qMin( segment.p1().x(), segment.p2().x());
  qreal xmax = qMax( segment.p1().x(), segment.p2().x());
  qreal ymin = qMin( segment.p1().y(), segment.p2().y());
  qreal ymax = qMax( segment.p1().y(), segment.p2().y());
  if( pt.x() >= xmin && pt.x() <= xmax &&
    pt.y() >= ymin && pt.y() <= ymax ) {
      QPointF v1 = pt - segment.p1();
      QPointF v2 = segment.p2() - segment.p1();
      if( qFuzzyCompare( v1.x()*v2.y()-v1.y()*v2.x(), 0 ) ) {
        result = true;
      }
  }

  return result;
}

bool SharedUtil::lineInterCircle( const QLineF& line, const QPointF& center, float radius, QPolygonF& interPts )
{
  float fDis = line.length();
  QPointF dVec = (line.p2() - line.p1()) / fDis;
  QPointF eVec = (center - line.p1());

  float aPrim = eVec.x()*dVec.x() + eVec.y()*dVec.y();
  float a2 = aPrim * aPrim;
  float e2 = eVec.x()*eVec.x() + eVec.y()*eVec.y();
  float r2 = radius * radius;
  if( (r2 - e2+a2) < 0 ) {
    return false;
  } else {
    float fPrim = sqrt( r2 - e2+a2 );
    float tPrim = aPrim - fPrim;
    if( (tPrim-0.0f) > -eps && (tPrim-fDis) < eps ) {
      interPts << line.p1() + tPrim*dVec;
    }

    tPrim = aPrim + fPrim;
    if( (tPrim-0.0f) > -eps && (tPrim-fDis) < eps ) {
      interPts << line.p1() + tPrim*dVec;
    }

    if( interPts.count() > 1 ) {
      if( QLineF(line.p1(), interPts.first()).length() > QLineF(line.p1(), interPts.last()).length() ) {
        qSwap( interPts[0], interPts[1] );
      }
    }

    return true;
  }
}

QRectF SharedUtil::getBoundingRect( const QPointF& p1, const QPointF& p2 )
{
  qreal xmin = qMin( p1.x(), p2.x() );
  qreal xmax = qMax( p1.x(), p2.x() );
  qreal ymin = qMin( p1.y(), p2.y() );
  qreal ymax = qMax( p1.y(), p2.y() );

  return QRectF(QPointF(xmin, ymin), QPointF(xmax, ymax));
}

QList<QPolygonF> SharedUtil::getSelfIntersectPoints( const QPolygonF& polyline )
{
  QList<QPolygonF> result;
  QVector<QRectF> bRects = getBoundingRects( polyline );
  result = QVector<QPolygonF>( bRects.count() ).toList();

  QPointF pt;
  for( int idx = 0; idx < bRects.count()-1; ++idx ) {
    const QRectF& rect1 = bRects.at( idx );
    QLineF line1(polyline.at(idx  ), polyline.at(idx+1));
    for( int idy = idx+2; idy < bRects.count(); ++idy ) {
      const QRectF& rect2 = bRects.at( idy );
      if( rect1.intersects( rect2 ) ) {
        QLineF line2(polyline.at(idy), polyline.at(idy+1));
        if( line1.intersect( line2, &pt ) == QLineF::BoundedIntersection ) {
          result[idx] << pt;
          result[idy] << pt;
        }
      }
    }
  }

  return result;
}

QPolygonF SharedUtil::getIntersectPoints( const QPolygonF& pline1, const QPolygonF& pline2, int intersectType )
{
  QPolygonF result;

  QVector<QRectF> bRects1 = getBoundingRects( pline1 );
  QVector<QRectF> bRects2 = getBoundingRects( pline2 );

  QPointF pt;
  for( int idx = 0; idx < bRects1.count(); ++idx ) {
    const QRectF& rect1 = bRects1.at( idx );
    QLineF line1(pline1.at(idx), pline1.at(idx+1));
    for( int idy = 0; idy < bRects2.count(); ++idy ) {
      const QRectF& rect2 = bRects2.at( idy );
      if( rect1.intersects( rect2 ) ) {
        QLineF line2(pline2.at(idy), pline2.at(idy+1));
        if( line1.intersect( line2, &pt )&intersectType ) {
          result << pt;
        }
      }
    }
  }

  return result;
}

void SharedUtil::getIntersectPoints( const QPolygonF& pline1, const QPolygonF& pline2,
                                     QList<QPolygonF>& pts1, QList<QPolygonF>& pts2 )
{
  QVector<QRectF> bRects1 = getBoundingRects( pline1 );
  QVector<QRectF> bRects2 = getBoundingRects( pline2 );

  pts1 = QVector<QPolygonF>( bRects1.count() ).toList();
  pts2 = QVector<QPolygonF>( bRects2.count() ).toList();

  QPointF pt;
  for( int idx = 0; idx < bRects1.count(); ++idx ) {
    const QRectF& rect1 = bRects1.at( idx );
    QLineF line1(pline1.at(idx), pline1.at(idx+1));
    for( int idy = 0; idy < bRects2.count(); ++idy ) {
      const QRectF& rect2 = bRects2.at( idy );
      if( rect1.isEmpty() || rect2.isEmpty() || rect1.intersects( rect2 ) ) {
        QLineF line2(pline2.at(idy), pline2.at(idy+1));
        if( line1.intersect( line2, &pt ) == QLineF::BoundedIntersection ) {
          pts1[idx] << pt;
          pts2[idy] << pt;
        }
      }
    }
  }
}

void SharedUtil::sortIntersectPoints( const QPolygonF& polyline, QList<QPolygonF>& intersectPoints )
{
  if( intersectPoints.count() != polyline.count()-1 ) {
    return;
  }

  for( int idx = 0; idx < intersectPoints.count(); ++idx ) {
    intersectPoints[idx] = sortPoints( polyline.at( idx ), intersectPoints.at( idx ) );
  }
}

//QList<QPolygonF> SharedUtil::clippingByCircle( float radius, const QPolygonF& polyline )
//{
//  QList<QPolygonF> result;
//
//  bool bInside = true;
//  const QPointF& center = polyline.first();
//
//  QPolygonF subLines;
//  for( int idx = 0; idx < polyline.count()-1; ++idx ) {
//    const QPointF& p1 = polyline.at( idx   );
//    const QPointF& p2 = polyline.at( idx+1 );
//    if( QLineF(p1, center).length() < radius && QLineF(p2, center).length() < radius ) {
//      continue;
//    }
//
//    QPolygonF interPts;
//    if( lineInterCircle( QLineF(p1, p2), center, radius, interPts ) ) {
//      if( bInside ) {
//        subLines.clear();
//        subLines << interPts << p2;
//        bInside = false;
//      } else {
//        if( !interPts.isEmpty() ) {
//          subLines << interPts.first();
//        } else {
//          subLines << p2;
//        }
//        result << subLines;
//        subLines.clear();
//        if( interPts.count() > 1 ) {
//          subLines << interPts.last() << p2;
//        } else {
//          bInside = true;
//        }
//      }
//    } else {
//      subLines << p2;
//    }
//  }
//
//  return result;
//}

QList<QPolygonF> SharedUtil::clippingByCircle( const QPointF& center, float radius, const QPolygonF& polyline )
{
  QList<QPolygonF> result;

  QPolygonF subLines;
  bool bInside = true;
  if( QLineF(polyline.first(), center).length() >= radius ) {
    bInside = false;
    subLines << polyline.first();
  }
  for( int idx = 0; idx < polyline.count()-1; ++idx ) {
    const QPointF& p1 = polyline.at( idx   );
    const QPointF& p2 = polyline.at( idx+1 );
    if( QLineF(p1, center).length() < radius && QLineF(p2, center).length() < radius ) {
      continue;
    }

    QPolygonF interPts;
    if( lineInterCircle( QLineF(p1, p2), center, radius, interPts ) ) {
      if( bInside ) {
        subLines.clear();
        subLines << interPts << p2;
        bInside = false;
      } else {
        if( !interPts.isEmpty() ) {
          subLines << interPts.first();
        } else {
          subLines << p2;
        }
        result << subLines;
        subLines.clear();
        if( interPts.count() > 1 ) {
          subLines << interPts.last() << p2;
        } else {
          bInside = true;
        }
      }
    } else {
      subLines << p2;
    }
  }
  if( !subLines.isEmpty() ) {
    result << subLines;
  }

  return result;
}
